/**
 * @author xiaobyu
 * @created Jun 8, 2016
 */
package mdt.program;

import org.tigris.gef.graph.MutableGraphSupport;
import mdt.model.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class MDTGraphModel extends MutableGraphSupport {

    private static final long serialVersionUID = 1L;
    protected List nodes = new ArrayList();
    protected List edges = new ArrayList();

    /**
     * constructor.
     *
     * @see org.tigris.gef.graph.MutableGraphSupport
     */
    public MDTGraphModel() {
        super();
    }

    /**
     * get all the nodes from the graphmodel/diagram
     *
     * @see org.tigris.gef.graph.MutableGraphSupport#getNodes()
     * @return Vector of nodes in the graphmodel/diagram
     */
    public List getNodes() {
        return nodes;
    }

    /**
     * get all the edges from the graphmodel/diagram
     *
     * @return Vector of edges in the graphmodel/diagram
     */
    public List getEdges() {
        return new ArrayList(edges);
    }

    /** Return a valid shapeNode in this graph */
    // public Object createNode(String name, Hashtable args) {
    // return null;
    // }

    /** Add the given shapeNode to the graph, if valid. */
    public void addNode(Object node) {
        nodes.add(node);
        fireNodeAdded(node);
    }

    /** Add the given edge to the graph, if valid. */
    public void addEdge(Object edge) {
        edges.add(edge);
        fireEdgeAdded(edge);
    }

    public void addClonedNode(Object node) {
        nodes.add(node);
    }

    /** Add the given edge to the graph, if valid. */
    public void addClonedEdge(Object edge) {
        edges.add(edge);
    }

    public boolean containsNode(Object node) {
        return nodes.contains(node);
    }

    public boolean constainsEdge(Object edge) {
        return edges.contains(edge);
    }

    /**
     * remove a shapeNode from the diagram and notify GEF
     *
     * @param node
     *          shapeNode to remove
     */
    public void removeNode(Object node) {
        if (!containsNode(node))
            return;
        nodes.remove(node);
        fireNodeRemoved(node);
    }

    /**
     * remove an edge from the graphmodel and notify GEF TODO
     * IllegalArgumentException or GraphModelException if edge not contained
     *
     * @param edge
     *          edge to remove
     */
    public void removeEdge(Object edge) {
        if (!containsEdge(edge))
            return;
        edges.remove(edge);
        fireEdgeRemoved(edge);
    }

    /**
     * Assume that anything can be connected to anything unless overridden in a
     * subclass.
     */
    public boolean canConnect(Object fromP, Object toP) {
        return true;
    }

    /**
     * The connect method without specifying a connection type is unavailable by
     * default
     */
    public Object connect(Object fromPort, Object toPort) {
        throw new UnsupportedOperationException("The connect method is not supported");
    }

    /**
     * Contruct and add a new edge of the given kind and connect the given ports.
     *
     * @param fromPort
     *          The originating port to connect
     *
     * @param toPort
     *          The destination port to connect
     *
     * @param edgeClass
     *          The NSUML type of edge to create.
     *
     * @return The type of edge created (the same as <code>edgeClass</code> if we
     *         succeeded, <code>null</code> otherwise)
     */
    public Object connect(Object fromPort, Object toPort, java.lang.Class edgeClass) {

        Object connection = null;

        if (fromPort == null || toPort == null) {
            return null;
        }

        if (canConnect(fromPort, toPort, edgeClass)) {

            IConnector fromConnector = (IConnector)fromPort;
            IConnector toConnector = (IConnector)toPort;
            if (edgeClass.equals(ISendMessage.class)) {
                MDTMessage dep = new SendMessage();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            } else if (edgeClass.equals(IReplyMessage.class)) {
                MDTMessage dep = new ReplyMessage();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            } else if (edgeClass.equals(IDualWayMessage.class)) {
                MDTMessage dep = new DualWayMessage();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            } else if (edgeClass.equals(IFailedMessage.class)) {
                MDTMessage dep = new FailedMessage();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            } else if (edgeClass.equals(ICommentLink.class)) {
                EdgeModel dep = new CommentLink();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            } else if (edgeClass.equals(ITextEdge.class)) {
                TextEdge dep = new TextEdge();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            } else if (edgeClass.equals(IArrowTextEdge.class)) {
                TextEdge dep = new ArrowTextEdge();
                dep.setSrcConnector(fromConnector);
                dep.setDstConnector(toConnector);
                connection = dep;
            }
            if (connection != null) {
                fromConnector.getConnectorOwner().addSrcDependency((IEdgeModel)connection);
                toConnector.getConnectorOwner().addDstDependency((IEdgeModel)connection);
            }
        }

        if (connection == null) {
            System.err.println("Cannot make a " + edgeClass.getName() + " between a " + fromPort.getClass().getName() + " and a "
                    + toPort.getClass().getName());
            return null;
        }

        addEdge(connection);
        System.out.println("Connection type" + edgeClass.getName() + " made between a " + fromPort.getClass().getName() + " and a "
                + toPort.getClass().getName());

        return connection;
    }

    /** Return all ports on shapeNode or edge */
    public Vector getPorts(Object nodeOrEdge) {
        Vector res = new Vector();
        // if (nodeOrEdge instanceof UmlClassifier) res.addElement(nodeOrEdge);
        // TODO Check if this association only has 2 edges
        // else if (nodeOrEdge instanceof UmlAssociation)
        // res.addElement(nodeOrEdge);
        return res;
    }

    /** Return the shapeNode or edge that owns the given port */
    public Object getOwner(Object port) {
        return port;
    }

    public void addNodeRelatedEdges(Object node) {
    }

    /** Return true if the given object is a valid edge in this graph */
    public boolean canAddEdge(Object edge) {
        return true;
    }

    /** Return true if the given object is a valid shapeNode in this graph */
    public boolean canAddNode(Object node) {
        return true;
    }

    /** Return one end of an edge */
    public Object getSourcePort(Object edge) {
        return null;
    }

    /** Return the other end of an edge */
    public Object getDestPort(Object edge) {
        return null;
    }

    /** Return all edges going to given port */
    public Vector getInEdges(Object port) {
        return null;
    }

    /** Return all edges going from given port */
    public Vector getOutEdges(Object port) {
        return null;
    }

    public boolean canChangeConnectedPort(Object newPort, Object oldPort,
                                          Object edge) {
        INodeModel newNode = ((IConnector)newPort).getConnectorOwner();
        INodeModel oldNode = ((IConnector)oldPort).getConnectorOwner();
        if ((oldNode instanceof INetworkDevice && newNode instanceof INetworkDevice)
                || (oldNode instanceof IDiamondChoice && newNode instanceof IDiamondChoice)
                && (edge instanceof IMDTMessage || edge instanceof ITextEdge))
            return true;
        return false;
    }

    public void changeConnectedPort(Object newPortObj, Object oldPortObj,
                                    Object edgeObj, boolean isSource) {

        IConnector oldConnector = (IConnector)oldPortObj;
        IConnector newConnector = (IConnector)newPortObj;

        INodeModel oldNode = oldConnector.getConnectorOwner();
        INodeModel newNode = newConnector.getConnectorOwner();

        IEdgeModel edge = (IEdgeModel)edgeObj;
        if (isSource) {
            oldNode.removeSrcDependency(edge);
            newNode.addSrcDependency(edge);
            edge.setSrcConnector(newConnector);
        } else {
            oldNode.removeDstDependency(edge);
            newNode.addDstDependency(edge);
            edge.setDstConnector(newConnector);
        }
    }
}
